﻿// first_ffmpeg.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <fstream>
using namespace std;

extern "C" { //指定函数是C语言函数，函数名不包含重载标注
//引用ffmpeg头文件
#include <libavcodec/avcodec.h>
}


//预处理指令导入库
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avutil.lib")

int main(int argc, char* argv[])
{
    string filename = "400_300_25";

    //支持两种编码格式
    AVCodecID codec_id = AV_CODEC_ID_H264;
    if (argc > 1)
    {
        string codec = argv[1];
        if (codec == "h265" || codec == "hevc")
        {
            codec_id = AV_CODEC_ID_HEVC;
        }
    }

    if (codec_id == AV_CODEC_ID_H264)
    {
        filename += ".h264";
    }
    else if (codec_id == AV_CODEC_ID_HEVC)
    {
        filename += ".h265";
    }
    //------支持两种编码格式

    //写入文件
    ofstream ofs;
    ofs.open(filename, ios::binary);

    //1 找到编码器 AV_CODEC_ID_HEVC(H265)
    auto codec = avcodec_find_encoder(codec_id);
    if (!codec)
    {
        cerr << "codec not find!" << endl;
        return -1;
    }

    //2 编码上下文
    auto context = avcodec_alloc_context3(codec);
    if (!context)
    {
        cerr << "avcodec_alloc_context3 failed!" << endl;
        return -1;
    }


    //3设定上下文参数
    context->width = 400; //视频宽高
    context->height = 300;

    //pts*time_base = 播放时间(秒), time_base是时间戳的“单位”
    //1/25的time_base适用于25fps视频​​，确保时间戳与帧率严格对应
    context->time_base = { 1,25 }; //分数1/25 {分子，分母}

    //元数据像素格式，编码算法相关
    context->pix_fmt = AV_PIX_FMT_YUV420P;

    //编码线程数，通过调用系统接口获取cpu核心数量
    //(此处先写死)
    context->thread_count = 16;

    //4打开编码上下文(发生错误概率较大)
    int re = avcodec_open2(context, codec, NULL);
    if (re != 0)
    {
        char buf[1024] = { 0 };
        av_strerror(re, buf, sizeof(buf) - 1);
        cerr << "avcodec_open2 failed!" << buf << endl;
        return -1;
    }

    cout << "avcodec_open2 success!" << endl;

    //创建好AVFrame空间 未压缩数据
    auto frame = av_frame_alloc();
    frame->width = context->width;
    frame->height = context->height;
    frame->format = context->pix_fmt;

    re = av_frame_get_buffer(frame, 0);
    if (re != 0)
    {
        char buf[1024] = { 0 };
        av_strerror(re, buf, sizeof(buf) - 1);
        cerr << "av_frame_get_buffer failed!" << buf << endl;
        return -1;
    }

    //av_packet每一帧空间不确定
    auto pkt = av_packet_alloc();
    //十秒视频 250帧
    for (int i = 0; i < 250; i++)
    {
        //生成AVFrame数据
        //Y
        for (int y = 0; y < context->height; y++)
        {
            for (int x = 0; x < context->width; x++)
            {
                //设定Y的色彩，等号后面是瞎填的
                frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
            }
        }

        //UV
        for (int y = 0; y < context->height / 2; y++)
        {
            for (int x = 0; x < context->width / 2; x++)
            {
                //设定UV的色彩，等号后面是瞎填的
                frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;
                frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5;
            }
        }

        frame->pts = i; //显示的时间

        //发送未压缩帧到线程中压缩
        re = avcodec_send_frame(context, frame);
        if (re != 0)
        {
            //发送出错一般不做过多处理，偶尔一帧出错很正常
            break;
        }

        while (re >= 0) //返回多帧
        {
        //接收压缩帧，一般前几次调用返回空(缓冲，立刻返回，编码未完成)
        //编码是在独立的线程中
        //每次调用会重新分配pkt中的空间
            re = avcodec_receive_packet(context, pkt);
            if (re == AVERROR(EAGAIN) || re == AVERROR_EOF)
            {
                //AVERROR(EAGAIN)输出不可用，需要再次发送输入内容
                //AVERROR_EOF读到文件结尾
                break;
            }
            if (re < 0)
            {
                char buf[1024] = { 0 };
                av_strerror(re, buf, sizeof(buf) - 1);
                cerr << "avcodec_receive_packet failed!" << buf << endl;
                return -1;
                break;
            }
            cout << pkt->size << " " << flush;
            //写入文件
            ofs.write((char*)pkt->data, pkt->size);

            //引用计数-1，释放av_packet指向的buffer空间
            av_packet_unref(pkt);

            //------while (re >= 0)
        }
        

        //------for (int i = 0; i < 250; i++)
    }
    
    ofs.close();
    av_packet_free(&pkt);

    av_frame_free(&frame);
    //释放上下文
    avcodec_free_context(&context);

    //cout << "first ffmpeg" << endl;
    //cout << avcodec_configuration() << endl;
    return 0;
}

// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单

// 入门使用技巧: 
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件，或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来，若要再次打开此项目，请转到“文件”>“打开”>“项目”并选择 .sln 文件
